package com.apples.hexviewer2;

import android.annotation.SuppressLint;
import android.content.DialogInterface;
import android.content.SharedPreferences;
import android.graphics.Color;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.ParcelFileDescriptor;
import android.os.SystemClock;
import android.preference.PreferenceManager;
import android.text.InputType;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.style.ForegroundColorSpan;
import android.view.ActionMode;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.WindowManager;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.EditText;
import android.widget.TextView;

import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.DialogFragment;

import com.apples.hexviewer2.databinding.ActivityOneBinding;
import com.apples.hexviewer2.models.FileData;
import com.apples.hexviewer2.models.LineEntry;
import com.apples.hexviewer2.utils.SysHelper;
import com.apples.hexviewer2.utils.UIHelper;
import com.google.android.material.textfield.TextInputLayout;
import com.rustamg.filedialogs.FileDialog;
import com.rustamg.filedialogs.OpenFileDialog;
import com.rustamg.filedialogs.SaveFileDialog;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.regex.Pattern;

/**
 * hex单文件编辑器
 *
 * @author WangKaiFang
 * @date 2024/08/28
 */
public class OneActivity extends AppCompatActivity implements FileDialog.OnFileSelectedListener {

    private ActivityOneBinding binding;
    private FileData fileData;
    private AlertDialog mDialog;
    protected TextView mTextView;
    protected long mTotalSize;
    protected long mCurrentSize;
    private String progressText;
    private ArrayList<LineEntry> listData;
    private volatile boolean mCancel;
    private ExecutorService executorService;
    private Handler handler;
    private Runnable updateRunnable;
    private int showMaxLines;
    private int maxLength;
    private int curLineNum;
    private int[] hLight;
    private long lCTime;
    private boolean isHexChanged;
    private long lastTime;
    public boolean alertShowing;

    @Override
    protected void onDestroy() {
        super.onDestroy();
        fileData = null;
        listData.clear();
        listData = null;
        mDialog = null;
        mTextView = null;
        mCancel = false;
        progressText = null;
        mCurrentSize = 0;
        mTotalSize = 0;
        binding = null;
        executorService.shutdown();
        executorService = null;
        handler = null;
        updateRunnable = null;
        showMaxLines = 0;
        maxLength = 0;
        curLineNum = 0;
        hLight = null;
        lCTime = 0;
        isHexChanged = false;
        lastTime = 0;
        alertShowing = false;
        System.gc();
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding = ActivityOneBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());
        setTitle(" ");
        initData();
    }

    private void initData() {
        listData = new ArrayList<>();
        executorService = Executors.newSingleThreadExecutor();
        handler = new Handler(Looper.getMainLooper());
        Uri data = getIntent().getData();
        if (data == null) {
            startActivity();
        } else {
            loadFileAlert(data);
        }
        initCustomMenu();
        binding.llRightCtr1.setOnClickListener(btnClick);
        binding.llHexAsciiCtr.setOnClickListener(btnClick);
        binding.btnLoadUp.setOnClickListener(btnClick);
        binding.btnLoadDown.setOnClickListener(btnClick);
        binding.btnGoAddr.setOnClickListener(btnClick);
        binding.btnSaveChange.setOnClickListener(btnClick);
        binding.btnSaveOverwrite.setOnClickListener(btnClick);
        binding.btnSaveAs.setOnClickListener(btnClick);
        binding.btnSearch.setOnClickListener(btnClick);
        binding.btnSearchUp.setOnClickListener(btnClick);
        binding.btnSearchDown.setOnClickListener(btnClick);
    }

    private final View.OnClickListener btnClick = view -> {
        if (SystemClock.uptimeMillis() - lastTime < 100) return;
        lastTime = SystemClock.uptimeMillis();
        if (!listData.isEmpty()) {
            if (view != binding.btnSearch && view != binding.btnSearchUp
                    && view != binding.btnSearchDown) {
                FunActivity.hideSoftKeyboard(this);
                binding.llSearchCtr2.setVisibility(View.GONE);
                binding.btnSearch.setVisibility(View.VISIBLE);
            }
            binding.etHexText.clearFocus();
            if (view == binding.btnLoadUp) {
                if (curLineNum == 0) return;
                int i = showMaxLines / 2;
                if (changeAlert((dialogInterface, i2) -> {
                    curLineNum = Math.max(curLineNum - i, 0);
                    fillHex2ET();
                })) {
                    curLineNum = Math.max(curLineNum - i, 0);
                    fillHex2ET();
                }
            } else if (view == binding.btnLoadDown) {
                if (curLineNum + showMaxLines >= listData.size()) return;
                int i = showMaxLines / 2;
                if (changeAlert((dialogInterface, i2) -> {
                    curLineNum = Math.min(curLineNum + i,
                            listData.size() - i);
                    fillHex2ET();
                })) {
                    curLineNum = Math.min(curLineNum + i,
                            listData.size() - i);
                    fillHex2ET();
                }
            } else if (view == binding.btnGoAddr) {
                goToAddress();
            } else if (view == binding.btnSaveChange) {
                if (isChanged()) {
                    holdHexContent();
                }
            } else if (view == binding.btnSaveOverwrite) {
                if (isHexChanged) {
                    save2File(fileData.getUri());
                } else {
                    FunActivity.showTip(this, getString(R.string.label_tip_msg15));
                }
            } else if (view == binding.btnSaveAs) {
                saveAsFile();
            } else if (view == binding.btnSearch) {
                if (changeAlert((dialogInterface, i2) -> {
                    binding.llSearchCtr2.setVisibility(View.VISIBLE);
                    binding.btnSearch.setVisibility(View.GONE);
                    binding.etSearchText.requestFocus();
                    FunActivity.showSoftKeyboard(binding.etSearchText);
                })) {
                    binding.llSearchCtr2.setVisibility(View.VISIBLE);
                    binding.btnSearch.setVisibility(View.GONE);
                    binding.etSearchText.requestFocus();
                    FunActivity.showSoftKeyboard(binding.etSearchText);
                }
            } else if (view == binding.btnSearchUp) {
                String s = binding.etSearchText.getText().toString().trim();
                if (!TextUtils.isEmpty(s)) {
                    s = s.toUpperCase(Locale.ENGLISH);
                    int ssIdx = curLineNum;
                    ssIdx--;
                    if (ssIdx < 0) ssIdx++;
                    for (int i = ssIdx; i >= 0; i--) {
                        LineEntry entry = listData.get(i);
                        String s1 = entry.getHex().replace(" ", "");
                        if (s1.contains(s)) {
                            gotoPosition(i);
                            break;
                        }
                    }
                }
            } else if (view == binding.btnSearchDown) {
                String s = binding.etSearchText.getText().toString().trim();
                if (!TextUtils.isEmpty(s)) {
                    s = s.toUpperCase(Locale.ENGLISH);
                    int ssIdx = curLineNum;
                    ssIdx++;
                    if (ssIdx == listData.size()) ssIdx--;
                    for (int i = ssIdx; i < listData.size(); i++) {
                        LineEntry entry = listData.get(i);
                        String s1 = entry.getHex().replace(" ", "");
                        if (s1.contains(s)) {
                            gotoPosition(i);
                            break;
                        }
                    }
                }
            }
        }
    };

    public void gotoPosition(int position) {
        curLineNum = position;
        if (curLineNum < 0) curLineNum = 0;
        if (curLineNum >= listData.size()) curLineNum = listData.size() - 1;
        fillHex2ET();
    }

    private void saveAsFile() {
        SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
        String xx = preferences.getString("hex_viewer_open_path", null);
        SaveFileDialog dialog = new SaveFileDialog();
        Bundle args = new Bundle();
        if (xx != null && !xx.isEmpty())
            args.putSerializable(FileDialog.START_DIRECTORY, new File(xx));
        dialog.setArguments(args);
        dialog.setOldFileName(fileData.getName());
        dialog.setStyle(DialogFragment.STYLE_NO_TITLE, R.style.Theme_HexViewerLibrary);
        dialog.show(getSupportFragmentManager(), SaveFileDialog.class.getName());
    }

    private void goToAddress() {
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setCancelable(false)
                .setTitle(R.string.action_go_to_address)
                .setPositiveButton(android.R.string.ok, null)
                .setNegativeButton(android.R.string.cancel, null);
        LayoutInflater factory = LayoutInflater.from(this);
        builder.setView(factory.inflate(R.layout.content_dialog_go_to, null));
        AlertDialog mDialog = builder.create();
        Objects.requireNonNull(mDialog.getWindow()).setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN |
                WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
        String title1 = getString(R.string.action_go_to_address);
        String title2 = getString(R.string.hexadecimal);
        long fSize = fileData.getSize();
        String mAddress = String.format(Locale.ENGLISH, "%0" + maxLength + "X", fSize);
        String mTitle = title1 + " (" + title2 + ") 0-" + mAddress;
        mDialog.setTitle(mTitle);
        mDialog.show();
        final EditText mEt = mDialog.findViewById(R.id.tieValue);
        final TextInputLayout mLayout = mDialog.findViewById(R.id.tilValue);
        if (mEt != null && mLayout != null) {
            mEt.setTextDirection(SysHelper.isRTL(this) ? View.TEXT_DIRECTION_RTL : View.TEXT_DIRECTION_LTR);
            mEt.setInputType(InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD);
            mEt.setText("0");
            mEt.addTextChangedListener(UIHelper.getResetLayoutWatcher(mLayout, true));
            mEt.setSelection(0, mEt.getText().length());
            mEt.requestFocus();
        }
        mDialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(view -> {
            String text = Objects.requireNonNull(mEt).getText().toString();
            if (text.isEmpty())
                return;
            if (!HEXADECIMAL_PATTERN.matcher(text).matches()) {
                displayError(mDialog, mLayout, mEt, mTitle, null);
                return;
            }
            int address = Integer.parseInt(text, 16);
            if (address > fSize) {
                String mAddress2 = String.format(Locale.ENGLISH, "%0" + maxLength + "X", fSize);
                displayError(mDialog, mLayout, mEt, mTitle, "0-" + mAddress2);
                return;
            }
            int position = address / 16;
            position = position >= listData.size() ? listData.size() - 1 : position;
            curLineNum = Math.max(0, position);
            fillHex2ET();
            mDialog.dismiss();
        });
        mDialog.getButton(DialogInterface.BUTTON_NEGATIVE).setOnClickListener(v -> mDialog.dismiss());
    }

    public static final Pattern HEXADECIMAL_PATTERN = Pattern.compile("\\p{XDigit}+");

    private void displayError(AlertDialog mDialog, TextInputLayout mLayout, EditText mEt, String mTitle, String err) {
        if (mLayout != null) {
            UIHelper.shakeError(mEt, null);
            mLayout.setError(err);
        } else {
            mDialog.dismiss();
            UIHelper.showErrorDialog(this, mTitle, err);
        }
    }

    private boolean changeAlert(DialogInterface.OnClickListener listener) {
        if (isChanged()) {
            new AlertDialog.Builder(this)
                    .setCancelable(false)
                    .setMessage(getString(R.string.label_tip_msg1))
                    .setPositiveButton(getString(R.string.label_tip_msg2),
                            (dialogInterface, i2) -> holdHexContent())
                    .setNegativeButton(getString(R.string.label_tip_msg3), listener)
                    .setNeutralButton(getString(R.string.label_button_cancel), null)
                    .create().show();
            return false;
        }
        return true;
    }

    private boolean isChanged() {
        String[] hexStrings = binding.etHexText.getText().toString().split("\n");
        int slineNum2 = curLineNum;
        for (String hexString : hexStrings) {
            String hex = listData.get(slineNum2).getHex();
            if (!hex.equals(hexString)) {
                return true;
            }
            slineNum2++;
        }
        return false;
    }

    private void holdHexContent() {
        if (SystemClock.uptimeMillis() - lCTime > 300) {
            lCTime = SystemClock.uptimeMillis();
            String text = binding.etHexText.getText().toString();
            if (text.trim().isEmpty()) {
                listData.subList(curLineNum, Math.min(curLineNum + showMaxLines, listData.size()))
                        .clear();
                curLineNum--;
                fillHex2ET();
                setTitle("*" + fileData.getName());
                isHexChanged = true;
            } else {
                String[] hexStrings = text.split("\n");
                int slineNum2 = curLineNum;
                boolean icge = false;
                for (String string : hexStrings) {
                    String validate = string.replace(" ", "")
                            .toUpperCase(Locale.ENGLISH);
                    if (!SysHelper.isValidHexLine(validate)) {
                        FunActivity.showTip(this, String.format("%0" + maxLength + "X  ",
                                slineNum2 * 16) + getString(R.string.error_invalid_value));
                        return;
                    }
                    if (!icge) {
                        String hex = listData.get(slineNum2).getHex();
                        if (!hex.equals(string)) {
                            icge = true;
                        }
                    }
                    slineNum2++;
                }
                if (icge) {
                    ArrayList<LineEntry> list = new ArrayList<>();
                    for (String string : hexStrings) {
                        String validate = string.replace(" ", "")
                                .toUpperCase(Locale.ENGLISH);
                        if (validate.isEmpty()) continue;
                        byte[] bytes = SysHelper.hexStringToByteArray(validate);
                        if (bytes.length > 16) {
                            List<byte[]> list1 = SysHelper.splitByteArray(bytes, 16);
                            for (byte[] bytes1 : list1) {
                                list.add(new LineEntry(bytes1));
                            }
                        } else {
                            list.add(new LineEntry(bytes));
                        }
                    }
                    listData.subList(curLineNum, Math.min(curLineNum + showMaxLines, listData.size()))
                            .clear();
                    listData.addAll(curLineNum, list);
                    fillHex2ET();
                    setTitle("*" + fileData.getName());
                    isHexChanged = true;
                }
            }
        }
    }

    private void loadFileDone() {
        maxLength = String.format(Locale.ENGLISH, "%X", fileData.getSize()).length();
        int lh = binding.tvNumberOrder.getLineHeight();
        int h = binding.tvNumberOrder.getHeight();
        showMaxLines = h / lh;
        byte[] idx = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
        binding.etHexText2.setText(LineEntry.lbthex(idx));
        binding.llRightCtr1.setVisibility(View.VISIBLE);
        fillHex2ET();
    }

    private void fillHex2ET() {
        StringBuilder order = new StringBuilder();
        StringBuilder hex = new StringBuilder();
        StringBuilder ascii = new StringBuilder();
        List<LineEntry> items = listData;
        int i1 = Math.min(curLineNum + showMaxLines, items.size());
        for (int i = curLineNum; i < i1; i++) {
            LineEntry lineEntry = items.get(i);
            hex.append("\n");
            hex.append(lineEntry.getHex());
            ascii.append("\n");
            ascii.append(lineEntry.getAscii());
            order.append("\n");
            order.append(String.format(Locale.ENGLISH, "%0" + maxLength + "X",
                    i * 16L));
        }
        order.deleteCharAt(0);
        hex.deleteCharAt(0);
        ascii.deleteCharAt(0);
        if (order.indexOf("\n") == -1)
            order.append('\n');
        if (hex.indexOf("\n") == -1)
            hex.append('\n');
        if (ascii.indexOf("\n") == -1)
            ascii.append('\n');
        binding.tvNumberOrder.setText(order);
        binding.etHexText.setText(hex);
        binding.tvAsciiText.setText(ascii);
        binding.tvNumberOrder.post(() -> {
            int w = binding.tvNumberOrder.getWidth();
            int w2 = binding.tvNumberOrder2.getWidth();
            if (w2 != w)
                binding.tvNumberOrder2.setWidth(w);
        });
    }

    private void save2File(final Uri uri) {
        if (uri == null) return;
        mDialog = new AlertDialog.Builder(this).create();
        mDialog.setCancelable(false);
        final View v = getLayoutInflater().inflate(R.layout.progress_dialog_hex, null);
        mTextView = v.findViewById(R.id.text);
        progressText = getString(R.string.saving) + " ";
        mTextView.setText(progressText);
        mCancel = false;
        v.findViewById(R.id.cancel).setOnClickListener(view -> mCancel = true);
        mDialog.setView(v);
        mDialog.show();
        executorService.execute(() -> {
            try {
                mCurrentSize = 0;
                mTotalSize = listData.size();
                ParcelFileDescriptor mFdInput = getContentResolver()
                        .openFileDescriptor(uri, "rw");
                if (mFdInput != null) {
                    FileOutputStream outputStream = new FileOutputStream(mFdInput.getFileDescriptor());
                    ArrayList<Byte> bytes = new ArrayList<>();
                    for (int i = 0; i < listData.size(); i++) {
                        LineEntry lineEntry = listData.get(i);
                        byte[] bytes1 = lineEntry.getRaw();
                        for (byte b : bytes1) {
                            bytes.add(b);
                        }
                        mCurrentSize = i + 1;
                        if (mCurrentSize % 640 == 0) {
                            byte[] bytes2 = FunActivity.convertListToByteArray(bytes);
                            outputStream.write(bytes2);
                            bytes.clear();
                            if (updateRunnable == null) {
                                updateRunnable = () -> {
                                    onProgressUpdate(false);
                                    updateRunnable = null;
                                };
                                handler.postDelayed(updateRunnable, 36);
                            }
                            if (mCancel) break;
                        }
                    }
                    if (!bytes.isEmpty()) {
                        byte[] bytes1 = FunActivity.convertListToByteArray(bytes);
                        outputStream.write(bytes1);
                    }
                    bytes.clear();
                    outputStream.close();
                    mFdInput.close();
                    handler.post(() -> {
                        mDialog.dismiss();
                        if (!mCancel) {
                            saveFileDone();
                        }
                    });
                }
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        });
    }

    private void saveFileDone() {
        isHexChanged = false;
        finish();
    }

    private void loadFile() {
        executorService.execute(() -> {
            try {
                mCurrentSize = 0;
                mTotalSize = fileData.getSize();
                ParcelFileDescriptor mFdInput = getContentResolver()
                        .openFileDescriptor(fileData.getUri(), "r");
                if (mFdInput != null) {
                    FileInputStream inputStream = new FileInputStream(mFdInput.getFileDescriptor());
                    byte[] buffer = new byte[1024 * 10];
                    int read;
                    while ((read = inputStream.read(buffer)) != -1) {
                        int offset = 0;
                        while (offset < read) {
                            int remainingBytes = read - offset;
                            int chunkSize = Math.min(16, remainingBytes);
                            byte[] chunk = new byte[chunkSize];
                            System.arraycopy(buffer, offset, chunk, 0, chunkSize);
                            listData.add(new LineEntry(chunk));
                            offset += chunkSize;
                            if (mCancel) break;
                        }
                        mCurrentSize += read;
                        if (updateRunnable == null) {
                            updateRunnable = () -> {
                                onProgressUpdate(true);
                                updateRunnable = null;
                            };
                            handler.postDelayed(updateRunnable, 36);
                        }
                        if (mCancel) break;
                    }
                    inputStream.close();
                    mFdInput.close();
                    handler.post(() -> {
                        mDialog.dismiss();
                        if (mCancel) {
                            finish();
                        } else {
                            loadFileDone();
                        }
                    });
                }
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        });
    }

    public void onProgressUpdate(boolean iLoad) {
        String text = progressText;
        if (iLoad) {
            text += SysHelper.sizeToHuman(this, mCurrentSize) + " / "
                    + SysHelper.sizeToHuman(this, mTotalSize);
        } else {
            text += String.format(Locale.ENGLISH, "%.2f%%",
                    (mCurrentSize * 1.0 / mTotalSize) * 100);
        }
        mTextView.setText(text);
    }

    public void startActivity() {
        SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
        String xx = preferences.getString("hex_viewer_open_path", null);
        OpenFileDialog dialog = new OpenFileDialog();
        Bundle args = new Bundle();
        args.putInt("oFn", 1);
        if (xx != null && !xx.isEmpty())
            args.putSerializable(FileDialog.START_DIRECTORY, new File(xx));
        dialog.setArguments(args);
        dialog.setStyle(DialogFragment.STYLE_NO_TITLE, R.style.Theme_HexViewerLibrary);
        dialog.show(this.getSupportFragmentManager(), OpenFileDialog.class.getName());
    }

    private void loadFileAlert(Uri data) {
        fileData = new FileData(this, data);
        setTitle(fileData.getName());
        mDialog = new AlertDialog.Builder(this).create();
        mDialog.setCancelable(false);
        final View v = getLayoutInflater().inflate(R.layout.progress_dialog_hex, null);
        mTextView = v.findViewById(R.id.text);
        progressText = getString(R.string.loading) + " ";
        mTextView.setText(progressText);
        mCancel = false;
        v.findViewById(R.id.cancel).setOnClickListener(view -> mCancel = true);
        mDialog.setView(v);
        mDialog.show();
        loadFile();
    }

    private void saveRecentlyPath(File file) {
        SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
        String paths = preferences.getString("hex_viewer_recently_path", null);
        String[] pathArr = null;
        int idx = -1;
        String s1 = file.getAbsolutePath();
        if (paths != null) {
            pathArr = paths.split("\\?\\|\\?");
            for (int i = 0; i < pathArr.length; i++) {
                if (s1.equals(pathArr[i])) {
                    idx = i;
                    break;
                }
            }
        }
        StringBuilder sbr = new StringBuilder("?|?");
        sbr.append(s1);
        if (pathArr != null) {
            for (int i = 0; i < pathArr.length; i++) {
                if (idx != -1 && i == idx) continue;
                sbr.append("?|?");
                sbr.append(pathArr[i]);
            }
        }
        SharedPreferences.Editor edit = preferences.edit();
        edit.putString("hex_viewer_recently_path", sbr.substring("?|?".length()));
        edit.apply();
    }

    @Override
    public void onFileSelected(FileDialog dialog, File file) {
        if (file == null) {
            finish();
        } else {
            int type = dialog.getType();
            Uri data = Uri.fromFile(file);
            if (type == 1) {
                saveRecentlyPath(file);
                loadFileAlert(data);
            } else if (type == 2) {
                save2File(data);
            }
        }
    }

    @Override
    public void onBackPressed() {
        if (isHexChanged) {
            new AlertDialog.Builder(this)
                    .setCancelable(false)
                    .setIcon(android.R.drawable.ic_dialog_alert)
                    .setTitle(R.string.action_close_title)
                    .setMessage(String.format(getString(R.string.confirm_save), fileData.getName()))
                    .setPositiveButton(R.string.yes, (dialog, which) -> {
                        save2File(fileData.getUri());
                        dialog.dismiss();
                    })
                    .setNegativeButton(R.string.no, (dialog, which) -> finish())
                    .setNeutralButton(R.string.cancel, (dialog, which) -> dialog.dismiss())
                    .show();
            return;
        }
        super.onBackPressed();
    }

    private void initCustomMenu() {
        binding.etHexText.setCustomSelectionActionModeCallback(new ActionMode.Callback() {
            @Override
            public boolean onCreateActionMode(ActionMode mode, Menu menu) {
                // 添加自定义的菜单项
                menu.add(0, 1, 0, getString(R.string.menu_crc16_hex));
                menu.add(0, 2, 1, getString(R.string.menu_sum_hex));
                return true;
            }

            private long ptm;

            @Override
            public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
                // 可以在这里更新菜单项
                MenuItem shareItem = menu.findItem(android.R.id.shareText);
                if (shareItem != null)
                    menu.removeItem(android.R.id.shareText);
                long upTimed = SystemClock.uptimeMillis();
                if (upTimed - ptm > 100) {
                    ptm = upTimed;
                    EditText editText = binding.etHexText;
                    int start = editText.getSelectionStart();
                    int end = editText.getSelectionEnd();
                    if (start != end) {
                        String sText = editText.getText().subSequence(0, start + 1).toString();
                        String sText2 = sText.replace(" ", "");
                        int sText3 = countSeparators(sText2, "\n");
                        sText2 = sText2.replace("\n", "");
                        int i2 = sText2.length() / 2 + sText3;
                        String selText = editText.getText().subSequence(start, end).toString();
                        String selText2 = selText.replace(" ", "");
                        int selText3 = countSeparators(selText2, "\n");
                        selText2 = selText2.replace("\n", "");
                        int i3 = i2 + selText2.length() / 2 + selText3;
                        FunActivity.selectText(binding.tvAsciiText, i2, i3);
                        hLight = new int[]{start, end};
                    }
                }
                return true;
            }

            @Override
            public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
                switch (item.getItemId()) {
                    case 1:
                        // 处理“CRC16计算”菜单项的点击事件
                        showCRCHTMLDialog();
                        return true;
                    case 2:
                        // 处理“和计算”菜单项的点击事件
                        actionSUM();
                        return true;
                    default:
                        return false;
                }
            }

            @Override
            public void onDestroyActionMode(ActionMode mode) {
                if (alertShowing) return;
                FunActivity.clearSelection(binding.tvAsciiText);
                binding.tvAsciiText.clearFocus();
            }
        });
        binding.tvAsciiText.setCustomSelectionActionModeCallback(new ActionMode.Callback() {
            @Override
            public boolean onCreateActionMode(ActionMode actionMode, Menu menu) {
                menu.add(0, 1, 0, getString(R.string.menu_crc16_hex));
                menu.add(0, 2, 1, getString(R.string.menu_sum_hex));
                return true;
            }

            private long ptm;

            @Override
            public boolean onPrepareActionMode(ActionMode actionMode, Menu menu) {
                // 可以在这里更新菜单项
                MenuItem shareItem = menu.findItem(android.R.id.shareText);
                if (shareItem != null)
                    menu.removeItem(android.R.id.shareText);
                long upTimed = SystemClock.uptimeMillis();
                if (upTimed - ptm > 100) {
                    ptm = upTimed;
                    TextView textView = binding.tvAsciiText;
                    int start = textView.getSelectionStart();
                    int end = textView.getSelectionEnd();
                    if (start != end) {
                        String sText = textView.getText().subSequence(0, start).toString();
                        int sText2 = countSeparators(sText, "\n");
                        sText = sText.replace("\n", "");
                        int i2 = sText.length() * 3 + sText2;
                        String selText = textView.getText().subSequence(start, end).toString();
                        int selText3 = countSeparators(selText, "\n");
                        selText = selText.replace("\n", "");
                        int i3 = i2 + selText.length() * 3 + selText3;
                        FunActivity.selectText(binding.etHexText, i2, i3);
                        hLight = new int[]{i2, i3};
                    }
                }
                return true;
            }

            @Override
            public boolean onActionItemClicked(ActionMode actionMode, MenuItem item) {
                switch (item.getItemId()) {
                    case 1:
                        // 处理“CRC16计算”菜单项的点击事件
                        showCRCHTMLDialog();
                        return true;
                    case 2:
                        // 处理“和计算”菜单项的点击事件
                        actionSUM();
                        return true;
                    default:
                        return false;
                }
            }

            @Override
            public void onDestroyActionMode(ActionMode actionMode) {
                if (alertShowing) return;
                FunActivity.clearSelection(binding.etHexText);
                binding.etHexText.clearFocus();
            }
        });
    }

    public int countSeparators(String input, String separator) {
        if (input == null || input.isEmpty()) {
            return 0;
        }
        return input.split(separator, -1).length - 1;
    }

    @SuppressLint("SetJavaScriptEnabled")
    private void showCRCHTMLDialog() {
        if (SystemClock.uptimeMillis() - lCTime < 333 || hLight == null) return;
        lCTime = SystemClock.uptimeMillis();
        int start = hLight[0];
        int end = hLight[1];
        if (start == 0 || end == 0)
            return;
        String s = binding.etHexText.getText().toString().substring(start, end);
        String s1 = s.replace(" ", "").replace("\n", "");
        if (TextUtils.isEmpty(s1)) return;
        if (s1.length() % 2 != 0) {
            FunActivity.showTip(this, getString(R.string.error_invalid_value));
            return;
        }
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setCancelable(false);
        View view = LayoutInflater.from(this).inflate(R.layout.dialog_hex_crc_webview,
                null);
        WebView wb = view.findViewById(R.id.wb_hex_crc);
        View pb = view.findViewById(R.id.progressBar);
        String filePath = "file:///android_asset/crccalc.com/index.htm?paramName=" + s1;
        WebSettings myWebSettings = wb.getSettings();
        myWebSettings.setJavaScriptEnabled(true);//设置支持javascript
        myWebSettings.setUseWideViewPort(true);//将图片调整到适合webView大小
        myWebSettings.setLoadWithOverviewMode(true);//缩放至屏幕大小
        myWebSettings.setDomStorageEnabled(true);//设置DOM缓存
        myWebSettings.setCacheMode(android.webkit.WebSettings.LOAD_NO_CACHE);//设置去缓存
        myWebSettings.setDatabaseEnabled(true);
        myWebSettings.setAllowFileAccess(true);
        myWebSettings.setAllowContentAccess(true);
        myWebSettings.setAllowUniversalAccessFromFileURLs(true);
        wb.setWebViewClient(new WebViewClient() {
            @Override
            public void onPageFinished(WebView view, String url) {
                super.onPageFinished(view, url);
                pb.postDelayed(() -> pb.setVisibility(View.INVISIBLE), 69);
            }
        });
        wb.loadUrl(filePath);
        builder.setView(view);
        builder.setPositiveButton(R.string.tip_alert_ok_hex, null);
        AlertDialog dialog = builder.create();
        dialog.setOnDismissListener(dialogInterface -> alertShowing = false);
        dialog.show();
        alertShowing = true;
    }

    private void actionSUM() {
        if (SystemClock.uptimeMillis() - lCTime < 333 || hLight == null) return;
        lCTime = SystemClock.uptimeMillis();
        int start = hLight[0];
        int end = hLight[1];
        if (start == 0 || end == 0)
            return;
        String s = binding.etHexText.getText().toString().substring(start, end);
        String s1 = s.replace(" ", "").replace("\n", "");
        if (TextUtils.isEmpty(s1)) return;
        if (s1.length() % 2 != 0) {
            FunActivity.showTip(this, getString(R.string.error_invalid_value));
            return;
        }
        String[] s2 = splitHexString(s1);
        int sum = 0;
        StringBuilder sbr = new StringBuilder();
        for (String hex : s2) {
            if (TextUtils.isEmpty(hex)) continue;
            sum += Integer.parseInt(hex, 16);
        }
        String result = Integer.toHexString(sum).toUpperCase();
        if (result.length() % 2 == 1)
            result = "0" + result;
        sbr.append(getString(R.string.label_tip_msg14)).append(result);
        AlertDialog dialog = new AlertDialog.Builder(this)
                .setCancelable(false)
                .setMessage(sbr)
                .setPositiveButton(R.string.tip_alert_ok_hex, null)
                .create();
        dialog.setOnDismissListener(dialogInterface -> alertShowing = false);
        dialog.show();
        alertShowing = true;
        TextView textView = dialog.findViewById(android.R.id.message);
        TextView okButton = dialog.getButton(DialogInterface.BUTTON_POSITIVE);
        okButton.setTextSize(20f);
        okButton.setTextColor(getColor(R.color.colorBlueSelectedHex));
        if (textView != null) {
            textView.setTextSize(24f);
            textView.post(() -> {
                String text = sbr.toString();
                SpannableString spannableString = new SpannableString(text);
                int start2 = text.indexOf(":");
                spannableString.setSpan(new ForegroundColorSpan(Color.WHITE),
                        0, start2 + 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
                spannableString.setSpan(new ForegroundColorSpan(Color.RED),
                        start2 + 1, sbr.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
                textView.setText(spannableString);
            });
        }
    }

    public String[] splitHexString(String hexString) {
        int length = hexString.length();
        String[] result = new String[length / 2];
        for (int i = 0; i < length; i += 2) {
            result[i / 2] = hexString.substring(i, i + 2);
        }
        return result;
    }
}